閱讀本篇文章前,仔細想想看
- 類別的靜態成員(Static Members)是什麼?與普通成員差異在哪?
- 什麼情況下會採用靜態成員的設計呢?
如果還沒理解完畢的話,可以先翻看前一篇文章喔!
有些沒有 OOP 經驗的讀者可能覺得 —— 類別要講的東西怎麼特別多。
是很多沒錯 XD —— 會用倒是覺得挺好用的~ 尤其結合 TypeScript 介面可以寫出還蠻牛逼的程式碼。
因此本篇就~正文開始吧!
筆者照樣用簡單的方式舉例,就以昨天講到的 CircleGeometry
類別進行延伸。以下是它的程式碼:
今天來搞一些神奇的功能 —— 假設每次計算圓形的面積時,與其呼叫物件的 area
方法,我們能不能夠改成類似呼叫物件屬性的方式計算出面積呢?
一種可行方式是這樣:
於是可以這樣使用 CircularGeometryV2
。(編譯並且用 node
執行結果如圖一)
圖一:正常地印出半徑為 2 的圓之面積的結果
不過這樣有兩個缺點:
randomCircle.area = 某數字
來強行覆寫掉面積的值,使得計算結果被破壞掉當然可以仿造過往的手法,把 area
換成 private
模式,並且宣告 getArea
的公用方法讓使用者可以呼叫並取得 area
的值。但這就跟本篇一開始想要實踐出的結果相違背啊!
於是筆者就把今天的主角請出場~存取方法(Accessors)。
存取方法分成兩種:取值與存值,也是時常聽到的 Getter Methods 或 Setter Methods。
貼心小提醒
儘管在 JS 裡將 Accessors 存取方法分別稱為 Getter/Setter Methods(取值/存值);然而某部分語言會將 Accessors 代表取值方法,而 Mutators 則是代表存值方法 —— 因此不是用 Getter/Setter 名稱之分,而是以 Accessor/Mutator 這樣的名稱來分。
根據今天想要達成的事情:想要藉由呼叫物件的 area
屬性取得圓的面積的值 —— 這裡的關鍵功能是『 取值 』,因此筆者先來介紹 Getter Method 怎麼使用。
以下是正確的實踐方式:
讀者可以發現,取值方法的實踐比想像中的簡單,就是用 get
關鍵字搭配想要取的名稱。
再次測試使用 CircleGeometryV2
的結果如圖二。
圖二:使用 get area
的特殊取值方法結果正常
若我們強行對 area
進行覆寫的動作,會被 TypeScript 送出罰單。(檢測結果如圖三;錯誤訊息如圖四)
圖三:TypeScript 會警告你,屬性 area
是不能被覆寫的
圖四:其中,TypeScript 吿訴我們的訊息很有趣 —— area
是唯讀的!(Read-only)
這裡出現了一個很有趣的結果:area
是唯讀的屬性,也就是說 —— 如果單純只有定義取值方法(Getter Method),它本身就是唯讀的狀態。
另外,使用存取方法(Accessors)令人感到方便的地方是 —— 只要物件的狀態被改變,存取方法們計算過後的值也會被自動更新!
主要是因為,一但那些藉由存取方法定義過後的物件屬性 —— 呼叫該屬性時,會根據存取方法裡面寫的程式進行計算。
所以測試以下的程式碼,radius
的屬性的值被改變後,area
屬性不需要主動去寫程式碼更新狀態,它就會根據取值方法(Getter Method)裡的內容計算出來。(以下程式碼編譯與 node
執行結果如圖五)
圖五:儘管 radius
被強行改變,我們的 area
也跟著被計算出來
讀者試試看
試著把
circumference
專門計算圓形周長的方法改成用 Getter Method 的方式實踐出來。
另外,我們還可以使用存值方法(Setter Methods)去更改物件屬性被指派值的狀況。這是什麼意思?
假設想將剛剛的 CircleGeometryV2
之 area
屬性 —— 原本是唯讀狀態 —— 改成每一次被指派值時,其半徑值 radius
也會跟著被調整成正確的值。因此,筆者這裡就直接示範使用 Setter Method 來達成剛剛所敘述的功能。
從以上的程式碼可以看出 —— 相對於取值方法 Getter Method,定義一個存值方法 Setter Method 則是用 set
關鍵字!
其中,要注意的一點是:因為存值方法專門在模擬屬性被指派值的情況,因此會需要一個參數去代表被指派的值,所以存值方法的型別會多一個參數 (value: number): void
。
以下筆者來驗證看看 CircleGeometryV2
的存取 area
這個屬性的行為會不會連同物件的半徑 radius
被更改。(編譯並且使用 node
執行之結果如圖六)
圖六:對 area
指派值,radius
也自動被更改了!
另外,筆者必須提到跟型別系統有關的重點(畢竟這可是 TypeScript 的主打 Feature 啊!)。
由於在 area
對應的取值方法(Setter Method)裡,其參數對應的是數字 number
型別 —— 也就是說,如果指派錯誤的型別到 area
去,TypeScript 也會自動幫我們監控喔!(偵測結果如圖七;訊息如圖八)
圖七:如果指派不為 number
型別的值到 area
屬性,由於 area
的存值方法的參數特別被註記為 number
型別,因此會出現錯誤
圖八:很明顯地,TypeScript 告訴我們不能將字串丟進去
相對於存值方法,取值方法也是有型別推論與註記的機制。如果對於函式型別篇章夠熟悉的話,TypeScript 會自動推論函式的輸出型態。
所以以下的程式碼,areaOfCircle
應該會被自動推論為數字 number
型別。(推論結果如圖九)
圖九:取得 area
的值時,也會被自動地被 TypeScript 推論出型別來喔!
取值方法(Getter Method)不能有任何參數:因為是模擬呼叫屬性的方式進行物件的取值,當然不會有參數的出現喔!(錯誤訊息如圖十)
圖十:TypeScript 扳著臉跟你講,get
Accessor 不能有任何參數
另外,取值方法的用意是模擬呼叫物件的屬性,因此 —— 取值方法沒有回傳任何值是錯誤的行為!(檢測結果如圖十一)
圖十一:TypeScript 會自動告訴你,你忘記回傳值了!
相對地,存值方法(Setter Method)只能有一個參數:因為是模擬指派任何值到屬性的方式進行物件的存值,而指派任何值只會被當成一個參數。(以下程式碼錯誤訊息如圖十二)
圖十二:TypeScript 也會提醒你不能有更多參數在 set
Accessor 裡面喔
重點 1. 類別的存取方法 Accessors
分成兩種:取值方法(Getter Method)與存值方法(Setter Method)。
- 取值方法專門在模擬呼叫物件的屬性時的行為;存值方法則是在模擬指派值到物件屬性的行為:由於兩者皆是用方法的方式來呈現屬性的呼叫與指派行為,因此才會被稱為存取方法。(而不是存取屬性)
- 若只有單純實踐某物件屬性的取值方法(Getter Method)而沒有相對應的存值方法,該屬性可以模擬唯讀(Read-only)的狀態。
- 取值方法的實踐不能有任何參數。若某屬性是利用取值方法來模擬的話,呼叫該物件的屬性,型別推論的結果會等同於取值方法回傳的值之型別。又因為是在模擬物件取值的過程,因此不回傳值的行為也是錯誤的!
- 存值方法只能有一個參數,而該參數代表的值是指派的值。根據函式型別篇章提出的重點,我們必須對存值方法內部的參數進行積極註記的動作。若某屬性的指派行為是用存值方法模擬,則該屬性被指派錯誤的值也會根據存值方法的參數被註記到的型別進行比對。
- 若想要在類別
C
宣告某存取方法模擬物件呼叫或指派值到屬性P
,其中P
必須被指派的值之型別為Tassign
,則程式碼的格式為:
readonly
也是可以在類別內使用的剛剛提到可以單純實踐取值方法(Getter Method)就可以模擬唯讀(Read-only)屬性的行為。
不過,在介面與型別裡可以用的 readonly
關鍵字在類別裡的成員變數裡也可以用!
假設我們的 CircleGeometryV2
裡 —— 圓周率 PI
原本是 private
模式,但是想要讓它既可以開放讀取但又可以防止被更改的話,可以直接加註為 readonly
並且宣告為 public
模式喔!
我們可以在外面取得 PI
的值但是確保它不會被更改。(檢測結果如圖十三;錯誤訊息如圖十四)
圖十三:如果 PI
被覆寫的話,就會被 TypeScript 提醒!
圖十四:TypeScript 提醒你,物件的 PI
屬性是 read-only
狀態喔
再者,不只是普通的成員變數,連**類別的靜態屬性也可以被標註為唯讀模式**呢~
所以如果強行覆寫 CircleGeometryV2
就會被警告呢。(檢測結果如圖十五;錯誤訊息如圖十六)
圖十五:有標註 readonly
就可以防止被外部覆寫
圖十六:CircleGeometryV2.staticPI
是唯讀(read-only)狀態,錯誤訊息也寫得好好的
重點 2. 類別裡使用
readonly
可以在類別的成員變數(Member Variables)與靜態屬性(Static Properties)標註
readonly
,代表該成員變數或靜態屬性是唯讀狀態。
今天我們又把類別的一項功能講完了 —— 也就是存取方法(Accessor)。
下一篇要講到一個很有趣的設計模式應用 —— 單例模式(Singleton Pattern),明天見~~~